package com.fujimana.android.manacode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import junit.framework.Assert;
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;

abstract class TreeAdapter<T> extends ArrayAdapter<T> {

	private class TreeNode {
		public TreeNode(T item, TreeNode parent, int depth) {
			this.targetObject = item;
			this.depth = depth;
			this.parent = parent;
		}

		T targetObject;
		@SuppressWarnings("unused")
		TreeNode parent;
		ArrayList<TreeNode> childNodes = new ArrayList<TreeNode>();
		int depth;
		boolean isExpanded = false;
		boolean isChildLoaded;
		boolean hasChildItems;
	}

	private HashMap<T, TreeNode> _objectToNodeMap = new HashMap<T, TreeNode>();
	private List<T> _sourceList;
	private TreeNode _root = new TreeNode(null, null, 0);
	private LinkedList<T> _targetList;

	private int[] _indentColors = new int[] { Color.WHITE, Color.GRAY };

	private int _indentWidth = 6;

	public TreeAdapter(Context context, int textViewResourceId, List<T> objects) {
		this(context, textViewResourceId, objects, new LinkedList<T>());
	}

	private TreeAdapter(Context context, int textViewResourceId,
			List<T> objects, LinkedList<T> targetList) {
		super(context, textViewResourceId, targetList);

		if (objects == null) {
			objects = new ArrayList<T>();
		}

		_sourceList = objects;

		_targetList = targetList;

		buildTree();

		treeToList();
	}

	public TreeAdapter(Context context, int textViewResourceId) {
		this(context, textViewResourceId, null);
	}

	@Override
	public void add(T object) {
		_sourceList.add(object);
		this.notifyDataSetChanged();
	};

	@Override
	public void remove(T object) {
		_sourceList.remove(object);
		this.notifyDataSetChanged();
	};

	@Override
	public void clear() {
		this._sourceList.clear();
		this.notifyDataSetChanged();
	}

	@Override
	public void notifyDataSetChanged() {

		treeToList();

		super.notifyDataSetChanged();
	}

	public abstract View getTreeItemView(T item, View convertView,
			ViewGroup parent);

	public abstract List<T> getChildObjects(T item);

	public boolean getIsExpandedByDefault(T item) {
		return true;
	}

	public void setIndentWidth(int width) {
		_indentWidth = width;
	}

	public void expand(T object) {
		if (_objectToNodeMap.containsKey(object)) {
			TreeNode info = _objectToNodeMap.get(object);
			if (info.isExpanded == false)
				changeOpenState(info, true);
		}
	}

	public void collapse(T object) {
		if (_objectToNodeMap.containsKey(object)) {
			TreeNode info = _objectToNodeMap.get(object);
			if (info.isExpanded == true)
				changeOpenState(info, false);
		}
	}

	public void toggleExpand(T object) {
		if (_objectToNodeMap.containsKey(object)) {
			TreeNode info = _objectToNodeMap.get(object);
			changeOpenState(info, !info.isExpanded);
		}
	}

	public boolean isExpanded(T object) {
		if (_objectToNodeMap.containsKey(object)) {
			return _objectToNodeMap.get(object).isExpanded;
		}

		return false;
	}

	public void setIndentColors(int[] indentColors) {
		_indentColors = indentColors;
	}

	class ViewHolder {
		LinearLayout rightPartLinearLayout;
		LinearLayout indentLinearLayout;
		View hLine;
		View targetView;
	}

	@SuppressWarnings("unchecked")
	@Override
	public final View getView(int position, View convertView, ViewGroup parent) {

		T object = _targetList.get(position);
		TreeNode info = _objectToNodeMap.get(object);
		T item = info.targetObject;

		View view = convertView;
		ViewHolder viewHolder;
		if (view == null) {
			viewHolder = new ViewHolder();

			LinearLayout container = new LinearLayout(this.getContext());
			container.setOrientation(LinearLayout.HORIZONTAL);
			view = container;
			container.setTag(viewHolder);

			LinearLayout rightPart = new LinearLayout(this.getContext());
			rightPart.setOrientation(LinearLayout.VERTICAL);

			View mainView = getTreeItemView(item, null, parent);
			viewHolder.targetView = mainView;

			rightPart.addView(mainView);
			viewHolder.rightPartLinearLayout = rightPart;

			LinearLayout indentViewContainer = new LinearLayout(
					this.getContext());
			indentViewContainer.setLayoutParams(new LayoutParams(
					LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT));
			indentViewContainer.setOrientation(LinearLayout.HORIZONTAL);
			viewHolder.indentLinearLayout = indentViewContainer;

			View line = new View(this.getContext());
			line.setLayoutParams(new LayoutParams(_indentWidth * 4, 1));
			rightPart.addView(line);
			viewHolder.hLine = line;

			container.addView(indentViewContainer);
			container.addView(rightPart);

		} else {
			viewHolder = (ViewHolder) view.getTag();
			for (int i = 0; i < viewHolder.indentLinearLayout.getChildCount(); i++) {
				addLineView(viewHolder.indentLinearLayout.getChildAt(i));
			}
			viewHolder.indentLinearLayout.removeAllViews();

			View mainView = getTreeItemView(item, viewHolder.targetView, parent);
			if (mainView != viewHolder.targetView) {
				viewHolder.rightPartLinearLayout
						.removeView(viewHolder.targetView);
				viewHolder.rightPartLinearLayout.addView(mainView, 0);
				viewHolder.targetView = mainView;
			}
		}

		if (info.hasChildItems) {
			viewHolder.hLine.setBackgroundColor(_indentColors[(info.depth)
					% _indentColors.length]);
		} else {
			viewHolder.hLine.setBackgroundColor(Color.TRANSPARENT);
		}

		if (info.depth == 0 && android.os.Build.VERSION.RELEASE.equals("1.6")) {
			View nullView = new View(this.getContext());
			nullView.setLayoutParams(new LayoutParams(0, 0));
			viewHolder.indentLinearLayout.addView(nullView);
		}

		for (int i = 0; i < info.depth + 0; i++) {
			View indentview = getLineView();

			indentview.setBackgroundColor(_indentColors[(i)
					% (_indentColors.length)]);
			indentview.setLayoutParams(new LayoutParams(_indentWidth,
					LayoutParams.FILL_PARENT));

			viewHolder.indentLinearLayout.addView(indentview);
		}

		return view;
	}

	private LinkedList<View> _lineViews = new LinkedList<View>();

	private void addLineView(View lineView) {
		_lineViews.add(lineView);
	}

	private View getLineView() {
		if (_lineViews.isEmpty()) {
			View indentview = new View(this.getContext());
			return indentview;
		} else {
			return _lineViews.remove();
		}
	}

	private void buildTree() {
		Assert.assertNotNull(_sourceList);

		_objectToNodeMap.clear();

		for (T t : _sourceList) {
			_root.childNodes.add(loadChildNodes(t, null, 0));
		}
	}

	private TreeNode loadChildNodes(T item, TreeNode parent, int depth) {
		TreeNode holder = new TreeNode(item, parent, depth);
		_objectToNodeMap.put(item, holder);

		List<T> childItems = getChildObjects(item);

		holder.hasChildItems = childItems != null && !childItems.isEmpty();
		holder.isExpanded = getIsExpandedByDefault(item);

		if (holder.hasChildItems && holder.isExpanded) {
			holder.isChildLoaded = true;

			for (T childItem : childItems) {
				TreeNode childNode = loadChildNodes(childItem, holder,
						depth + 1);
				holder.childNodes.add(childNode);
			}
		}

		return holder;
	}

	private void treeToList() {
		if (!_targetList.isEmpty())
			_targetList.clear();

		recurseAddList(_root.childNodes);
	}

	private void recurseAddList(List<TreeNode> nodes) {
		for (TreeNode n : nodes) {
			_targetList.add(n.targetObject);
			if (n.isExpanded)
				recurseAddList(n.childNodes);
		}
	}

	private void changeOpenState(TreeNode info, boolean expand) {
		Assert.assertTrue(info.isExpanded != expand);

		info.isExpanded = expand;

		if (expand) {
			if (info.hasChildItems) {
				if (!info.isChildLoaded) {
					info.isChildLoaded = true;

					List<T> childObjects = getChildObjects(info.targetObject);
					if (childObjects != null) {
						for (T childObject : childObjects) {
							info.childNodes.add(loadChildNodes(childObject,
									info, info.depth + 1));
						}
					}
				}

				int index = _targetList.indexOf(info.targetObject);
				insert(info.childNodes, _targetList.listIterator(index + 1));

			}
		} else {
			int index = _targetList.indexOf(info.targetObject);
			remove(info.childNodes, _targetList.listIterator(index + 1));
		}

		super.notifyDataSetChanged();
	}

	private void remove(ArrayList<TreeNode> nodes, ListIterator<T> listIterator) {
		for (TreeNode n : nodes) {

			listIterator.next();
			listIterator.remove();

			if (n.isExpanded)
				remove(n.childNodes, listIterator);
		}
	}

	private void insert(List<TreeNode> nodes, ListIterator<T> listIterator) {
		for (TreeNode n : nodes) {

			listIterator.add(n.targetObject);

			if (n.isExpanded)
				insert(n.childNodes, listIterator);
		}
	}
}